using System;
using System.Collections.Generic;
using Headsight.BPMN2WF.BPMN.Model;
using Headsight.BPMN2WF.BPMN.Schemas;
using Headsight.BPMN2WF.BPMN.Utils;

namespace Headsight.BPMN2WF.BPMN.BlockModel
{
    public class BPMNFlowNodesBlock2BlockTransformerImpl2 : IBPMNFlowNodesBlock2BlockTransformer
    {
        public BPMNFlowNodesBlock Transform(BPMNFlowNodesBlock originalBpmnFlowNodesBlock)
        {
            return CreateProcessBlock(originalBpmnFlowNodesBlock, originalBpmnFlowNodesBlock.FlowNodeIn);
        }

        private BPMNFlowNodesBlock CreateProcessBlock(BPMNFlowNodesBlock bpmnProcess, BPMNFlowNode startFlowNode)
        {
            var result = new BPMNFlowNodesBlock(startFlowNode) {BPMNSequenceFlowsTest = bpmnProcess.BPMNSequenceFlows};

            var startFlowNodesNumOfOutgoingFlows = bpmnProcess.GetNumberOfOutgoingFlows(startFlowNode);

            // No outgoing flows: finished (?). There could be "events"...!
            if (startFlowNodesNumOfOutgoingFlows == 0)
            {
                return result;
            }

            var t = startFlowNode.TElement as tGateway;
            

            // One outgoing flow
            if (startFlowNodesNumOfOutgoingFlows == 1 && 
                (t == null || t.gatewayDirection == tGatewayDirection.Converging))
            {
                var bpmnFlowNode = CreateNextFlowNode(result, 
                    bpmnProcess, bpmnProcess.GetTargetBPMNFlowNodes(startFlowNode)[0]);

                result.AddFlow(startFlowNode, bpmnFlowNode);

                var reducedBPMNProcess = bpmnProcess.Reduce(bpmnFlowNode);

                Recursive(result, reducedBPMNProcess, bpmnFlowNode);

            }
            // Diverging flow
            else // if (eachsNumOfOutgoingFlows > 1)
            {
                CreateDiverging(result, bpmnProcess, startFlowNode);
            }

            return result;
        }

        private void Recursive(BPMNFlowNodesBlock result, BPMNFlowNodesBlock bpmnProcess, BPMNFlowNode startFlowNode)
        {
            foreach (var each in bpmnProcess.GetTargetBPMNFlowNodes(startFlowNode))
            {
                var bpmnFlowNode = CreateNextFlowNode(result, bpmnProcess, each);

                result.AddFlow(startFlowNode, bpmnFlowNode);

                var reducedBPMNProcess = bpmnProcess.Reduce(bpmnFlowNode);

                Recursive(result, reducedBPMNProcess, bpmnFlowNode);
            }
        }

        private BPMNFlowNodesBlock CreateDiverging(BPMNFlowNodesBlock result, BPMNFlowNodesBlock bpmnProcess, BPMNFlowNode startFlowNode)
        {
            var convergingNode = bpmnProcess.GetConvergingFlowNode(startFlowNode);

            var createFlowNodes = new List<BPMNFlowNode>();
            foreach (var eachNextFlowNode in bpmnProcess.GetTargetBPMNFlowNodes(startFlowNode))
            {
                if (eachNextFlowNode == convergingNode)
                {
                    var bpmnProcessBlockEmpty = new BPMNFlowNodesBlockEmpty(
                        String.Format("empty_block_{0}", convergingNode.Id), convergingNode);

                    /*
                    new BPMNFlowNode(new tUserTask() { id = Guid.NewGuid().ToString("B") }, 
                                                                                           new List<BPMNParameterSoR>(), 
                                                                                           new List<BPMNParameterSoR>(), 
                                                                                           new List<BPMNSequenceFlow>(), new List<BPMNSequenceFlow>()));
                     */
                    result.AddFlow(startFlowNode, bpmnProcessBlockEmpty);
                    createFlowNodes.Add(bpmnProcessBlockEmpty);
                }
                else
                {
                    // from each2 till convergingNode
                    var bpmnSubProcess = bpmnProcess.GetBPMNSubProcess(
                        //bpmnProcess.GetTargetBPMNFlowNodes(startFlowNode), 
                        eachNextFlowNode, convergingNode);
                    var nodeOut = 
                        bpmnProcess.GetPreviousFlowNode(eachNextFlowNode, convergingNode);
                    var bpmnSubProcessBlock = 
                        CreateProcessBlock(bpmnSubProcess, eachNextFlowNode);
                    bpmnSubProcessBlock.NodeOut = nodeOut;

                    createFlowNodes.Add(bpmnSubProcessBlock);
                    result.AddFlow(startFlowNode, bpmnSubProcessBlock);
                }
            }

            // Converging node...
            var abc = convergingNode;// CreateNextFlowNode(result, bpmnProcess, convergingNode);

            if (convergingNode != null)
            {

                foreach (var each4 in createFlowNodes)
                {
                    // Link previously created nodes with converging node's result
                    // if converging nodes has an incoming flow from each4
                    if (bpmnProcess.IsAncestorOf(each4.BPMNFlowNodeOrNodeIn(), convergingNode) || each4 is BPMNFlowNodesBlockEmpty)
                    {
                        result.AddFlow(each4, abc);
                    }
                    else
                    {
                        // direct flow from flowStart into converging node
                    //    result.AddFlowFrom(startFlowNode, abc);
                    }
                }

                if (bpmnProcess.GetNumberOfOutgoingFlows(convergingNode) > 0)
                {
                    result.NodeOut = abc;
                }
            }

            return result;
        }

        private BPMNFlowNode CreateNextFlowNode(BPMNFlowNodesBlock parentProcessBlock, BPMNFlowNodesBlock bpmnProcess, BPMNFlowNode startFlowNode)
        {
            var startFlowNodesNumOfOutgoingFlows = 
                bpmnProcess.GetNumberOfOutgoingFlows(startFlowNode);

            // No outgoing flows: finished (?). There could be "events"...!
            if (startFlowNodesNumOfOutgoingFlows == 0)
            {
                return startFlowNode;
            }

            // One outgoing flow
            if (startFlowNodesNumOfOutgoingFlows == 1)
            {
                var result = CreateNextFlowNode(parentProcessBlock,
                    bpmnProcess, bpmnProcess.GetTargetBPMNFlowNodes(startFlowNode)[0]);

                parentProcessBlock.AddFlow(startFlowNode, result);

                return startFlowNode;
            }
            // Diverging flow
            else // if (eachsNumOfOutgoingFlows > 1)
            {
                var result = CreateProcessBlock(bpmnProcess, startFlowNode);

//                parentProcessBlock.AddFlowFrom(parentFlowNode, result);

                return result;
            }
        }
    }
}